// SSLLib.cpp : Defines the entry point for the DLL application.
//

#include "stdafx.h"
#include "SSLLib.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
    return TRUE;
}

int certificateValidationCallback(
   DWORD dwType,
   LPVOID pvArg,
   DWORD dwChainLen,
   LPBLOB pCertChain,
   DWORD dwFlags)
{
	int nRet;

	//TODO: more cert checking here? WM5 should be checking the domain for us at least.

	nRet = SSL_ERR_OKAY;

	if (dwFlags & SSL_CERT_FLAG_ISSUER_UNKNOWN)
	{
		nRet = SSL_ERR_CERT_UNKNOWN; 
	}

	return nRet;
}

DllExport int enumerateconnections(int pIndex, LPWSTR pConnInfo)
{
	CONNMGR_DESTINATION_INFO _info;
	HRESULT _res;
	
	memset(&_info, 0, sizeof(CONNMGR_DESTINATION_INFO));
	
	_res = ConnMgrEnumDestinations(pIndex, &_info);
	if (_res == E_FAIL)
	{
		return -1;
	}

	wsprintf(pConnInfo, TEXT("%s"), _info.szDescription);
	return 0;
}

GUID getGuid(int pIndex)
{
	CONNMGR_DESTINATION_INFO _info;
	HRESULT _res;
	GUID _ret;
	memset(&_ret, 0, sizeof(GUID));

	memset(&_info, 0, sizeof(CONNMGR_DESTINATION_INFO));
	
	_res = ConnMgrEnumDestinations(pIndex, &_info);
	if (_res == E_FAIL)
	{
		return _ret;
	}

	return _info.guid;
}

DllExport int ensureconnmgrready(LPWSTR pErrorString)
{
	HANDLE _manager = NULL;
	_manager = ConnMgrApiReadyEvent();
	
	if (!_manager)
	{
		wsprintf(pErrorString,TEXT("could not initialize connection manager."));
		return -1;
	}

	//wait for api to be ready.
	DWORD _res = WaitForSingleObject(_manager, 1000);

	BOOL _available = FALSE;
	if (_res == WAIT_OBJECT_0)
		_available = TRUE;

	if (_manager)
		CloseHandle(_manager);

	if (!_available)
	{
		wsprintf(pErrorString,TEXT("connection manager is not available."));
		return -1;
	}
	return 0;
}

DllExport int ensureconnected(LPWSTR pErrorString, LPWSTR pHostName, bool pUseSpecific, int pSpecificNetwork)
{
	GUID _destinationNetwork;

	DWORD _index = 0;
	HRESULT _res;

	if (!pUseSpecific)
	{
		_res = ConnMgrMapURL(pHostName, &_destinationNetwork, &_index);
		if (_res == E_FAIL)
		{
			wsprintf(pErrorString,TEXT("could not find appropriate destination network."));
			return -1;
		}
	}

	HANDLE _conn;
	DWORD _status = 0;
	DWORD _timeout = 20000;
	CONNMGR_CONNECTIONINFO _info;

	memset(&_info, 0, sizeof(CONNMGR_CONNECTIONINFO));
	_info.cbSize = sizeof(CONNMGR_CONNECTIONINFO);
	_info.dwParams = CONNMGR_PARAM_GUIDDESTNET;
	_info.dwFlags = CONNMGR_FLAG_PROXY_HTTP;
	_info.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
	if (pUseSpecific)
		_info.guidDestNet = getGuid(pSpecificNetwork);
	else
		_info.guidDestNet = _destinationNetwork;

	_res = ConnMgrEstablishConnectionSync(&_info, &_conn,_timeout,&_status);
	if (_res == E_FAIL)
	{
			wsprintf(pErrorString,TEXT("could not connect to destination network."));
			return -1;
	}

	return 0;
}

DllExport int sslsocketclose(int pSocket, int &pError)
{
	int _err = closesocket(pSocket);
	if (_err == SOCKET_ERROR) 
	{
		pError = WSAGetLastError();
	}
	WSACleanup();
	return _err;
}

DllExport int sslsocket(LPWSTR pHostName, int pPort, LPWSTR pErrorString)
{
	//we are using ATL conversion stuff.
	USES_CONVERSION;

	WORD _version;
	WSADATA _data;
	int _err = 0;
	
	_version = MAKEWORD( 2, 2 );
 
	_err = WSAStartup( _version, &_data );
	if ( _err != 0 ) {
		//couldn't startup winsock 
		wsprintf(pErrorString,TEXT("could not startup"));
		return -1;
	}
 
	//verify 2.2 is available.
	if ( LOBYTE( _data.wVersion ) != 2 ||
        HIBYTE( _data.wVersion ) != 2 ) {
		//wrong version available.
		wsprintf(pErrorString,TEXT("wrong version. %d"),WSAGetLastError());
		WSACleanup( );
		return -1; 
	}

	SOCKET _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	struct sockaddr_in _addr;
	memset (&_addr, 0, sizeof(_addr));

	//.net cf uses all unicode strings. we need to convert back and forth.
	char* _asciiHostName = W2A(pHostName);

	//resolve host name.
	hostent* _host = gethostbyname(_asciiHostName);

	if (_host == NULL)
	{
		//the host specified couldn't be found.
		wsprintf(pErrorString,TEXT("can't find host. %d"),WSAGetLastError());
		WSACleanup( );
		return -1;
	}

	char* _hostIP = inet_ntoa (*(struct in_addr *)*_host->h_addr_list);

	_addr.sin_family = AF_INET;
	_addr.sin_addr.s_addr = inet_addr(_hostIP);
	_addr.sin_port = htons((u_short)pPort);

	//set that we require a secure socket.
	DWORD _optVal = SO_SEC_SSL;
	DWORD _bytes = 0;
	_err = setsockopt(_socket, SOL_SOCKET, 
         SO_SECURE, (LPSTR)&_optVal, sizeof(_optVal));

	if (SOCKET_ERROR == _err){
		//we have encountered an error trying to make the socket secure.
		wsprintf(pErrorString,TEXT("can't set sock opt. %d"),WSAGetLastError());
		WSACleanup( );
		return -1;
	}

	//we need to specify a validation callback for the certificates for this to
	//work at all.
	SSLVALIDATECERTHOOK sslValidateFunc;
	sslValidateFunc.HookFunc = certificateValidationCallback;
    sslValidateFunc.pvArg = _asciiHostName; 

	//try and set the callback function
	_err = WSAIoctl(_socket, SO_SSL_SET_VALIDATE_CERT_HOOK,
         &sslValidateFunc, sizeof(sslValidateFunc), NULL, 0, &_bytes, NULL, NULL);

	if (SOCKET_ERROR == _err){
		//we encountered an error while trying to set up the validation callback.
		wsprintf(pErrorString,TEXT("can't set cert validation callback. %d"), WSAGetLastError());
		WSACleanup( );
		return -1;
	}

	//in WM5 we can get the API to do some validation on the domain for the certificate at least.
	_err = WSAIoctl(_socket, SO_SSL_SET_PEERNAME,
		_asciiHostName, strlen(_asciiHostName)+1, NULL, 0, NULL, NULL, NULL);

	if (SOCKET_ERROR == _err){
		// we had trouble telling WM5 that it should validate the domain on the cert.
		wsprintf(pErrorString,TEXT("can't set expected host name. %d"), WSAGetLastError());
		WSACleanup( );
		return -1;
	}

	// connect the socket.
	_err = connect(_socket, 
      (sockaddr *)&_addr, sizeof (sockaddr_in));

	if (_err == SOCKET_ERROR) {
		// we had an error connecting the socket
		wsprintf(pErrorString,TEXT("could not connect the socket. %d", WSAGetLastError()));
		WSACleanup( );
		return -1;
	}
	
	return _socket;
}

DllExport int sslrecv(int pSocket, LPWSTR pBuffer, int timeout_minutes, int &pError)
{
	//uses the ATL conversion stuff.
	USES_CONVERSION;

	fd_set sock_set;
	FD_ZERO(&sock_set);
	FD_SET(pSocket, &sock_set);

	TIMEVAL _tv;
	//timeout_minutes is the number of minutes before the select should timeout.
	_tv.tv_sec = timeout_minutes * 60;
	_tv.tv_usec = 0;

	int _ret;
	//wait till we timeout or there is data available on our socket.
	_ret = select(FD_SETSIZE, &sock_set, NULL, NULL, &_tv);
	
	//there has been an error trying to select.
	if (_ret == SOCKET_ERROR)
	{
		pError = WSAGetLastError();
		return _ret;
	}
	//we have timed out.
	if (_ret == 0)
		return _ret;

	int _bytes;
	char buff[1024];
	memset(buff,0,1024);

	//there is new data, receive it.
	_bytes = recv(pSocket,buff,1023,0);

	//we have encountered an error.
	if (_bytes <= 0)
		return _bytes;

	//we are ok, convert the bytes received to unicode and return.
	LPWSTR wbuff = A2W(buff);
	wcscpy_s(pBuffer,1023,wbuff);

	return _bytes;
}

DllExport int sslsend(int pSocket, LPWSTR pBuffer, int &pError)
{
	//we are using ATL conversion stuff.
	USES_CONVERSION;
	
	//all the .net cf strings are unicode.
	char* buff = W2A(pBuffer);
	int _err = send(pSocket, buff, strlen(buff), 0);
	if (_err == SOCKET_ERROR)
	{
		pError = WSAGetLastError();
		return _err;
	}
	return _err;
}